package server;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Vector;

import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;

import network.Packet;
import network.Packet_ActorRequest;
import network.Packet_Actors;
import network.Packet_Alive;
import network.Packet_Chunk;
import network.Packet_Message;
import network.Packet_Player;
import network.Packet_Request;
import network.Packet_State;
import network.Packet_Update;
import network.Player;
import network.Receiver;
import network.Packet.packettype;
import network.Packet_Message.MessageType;
import network.SpecialReceiver;

import server.Entity.EntityType;
import shared.UnifiedClock;
import shared.Vec2f;
import vmo.Actor;
import vmo.Actor_PlayerGhost;
import vmo.Chunk;
import vmo.Game;

public class Server implements Runnable{
	
	boolean m_keeprunning;
	UnifiedClock m_clock;
	int m_timestamp;
	Thread m_async;
	SpecialReceiver m_receiver;
	long m_lastframe;
	DatagramSocket m_outsocket;
	Vector <Player> m_players;
	
	Simulation m_simulation;
	
	public Server()
	{

		m_simulation=new Simulation();
		m_keeprunning=true;
		m_clock=new UnifiedClock();
		//setup player list
		m_players=new Vector<Player> ();
		//setup ports for server
		m_receiver=new SpecialReceiver(9876);
		m_async=new Thread(m_receiver);
		m_async.start();
		try {
			m_outsocket=new DatagramSocket();
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		m_timestamp=0;
		m_clock.SyncClock(0);
		m_lastframe=Sys.getTime();	
	}
	
	void PacketProcessing(Packet packet)
	{
		HandleLastreceived(packet);
	
		switch(packet.getType())
		{
			case MESSAGE:
				HandleMessage(packet);
			break;
			case PING:
				HandlePing(packet);
				break;
			case ALIVE:
				HandleAlive(packet);
				break;
			case REQUEST:
				HandleRequest((Packet_Request) packet);
				break;
			case ACTOR_REQUEST:
				HandleActorRequest((Packet_ActorRequest) packet);
				break;
			case UPDATE:
				HandleUpdate((Packet_Update) packet);
				break;
		}

		
	}
	
	void HandleUpdate(Packet_Update packet)
	{
		//find the equivalent player
		Player p=getPlayer(packet.getAddress());
		//find the ghost
		Actor_PlayerGhost ghost=(Actor_PlayerGhost) m_simulation.m_entities.get(p.m_player.m_entity).Find(p.m_player.m_UID);
		if (packet.getTimestamp()>p.m_lastupdate)
		{
			//figure out offset
			//remember, 12 frames in the PAST		
			int offset=packet.getTimestamp()-(m_timestamp-12);
			//negative value means discard frames
			//positive value means offset future wards
			
			System.out.print(offset);
			System.out.print(" timestamp ");
			System.out.print(m_timestamp-6);
			System.out.print(" packetstamp ");
			System.out.print(packet.getTimestamp());
			System.out.println();
			if (offset>-12 && offset<12)
			{
			//load the records from the packet into the ghost
				ghost.LoadRecord(offset, packet.m_record);

			}			
			p.m_lastupdate=packet.getTimestamp();
		}	
	}
	
	Player getPlayer(InetAddress address)
	{
		for (int i=0;i<m_players.size();i++)
		{
			byte []p=m_players.get(i).getAddress().getAddress();
			byte []c=address.getAddress();
			short d=0;
			for (int j=0;j<c.length;j++)
			{
				if (p[j]==c[j])
				{
					d++;
				}
			}
			if (d==c.length)
			{	
				return m_players.get(i);
			}
		}
		return null;
	}
	
	void HandleActorRequest(Packet_ActorRequest packet)
	{
		//find player
		Player p=getPlayer(packet.getAddress());
		if (p!=null)
		{
			Entity e=m_simulation.m_entities.get(p.m_player.m_entity);
			//construct packet actors and transmit it to the player
			Actor [] buffer=new Actor[packet.m_length];
			for (int i=0;i<packet.m_length;i++)
			{
				for (int j=0;j<e.m_actors.size();j++)
				{
					if (e.m_actors.get(j).getUID()==packet.m_uids[i])
					{
						buffer[i]=e.m_actors.get(j);
						break;
					}
				}
			}
			Packet_Actors pa=new Packet_Actors((int) (m_timestamp),buffer);
			p.m_lastsent=(int) (m_timestamp);
			Transmit(pa,packet.getAddress(),true);		
		}
	}
		
	void HandleRequest(Packet_Request packet)
	{
		Entity entity=m_simulation.m_entities.get(packet.m_entity);
		if (packet.m_y>=0 && packet.m_y<entity.m_height)
		{
			if (packet.m_x>=0 && packet.m_x<entity.m_width)
			{
				byte b=0b0001;	
				for (int i=0;i<4;i++)
				{
					if ((b & packet.m_bitfield)>0)
					{	
						Packet_Chunk chunk=new Packet_Chunk(packet.m_instance,packet.m_z, (byte)i, m_timestamp, 
							entity.m_chunks[packet.m_x][packet.m_y], packet.getAddress());
						Transmit(chunk,chunk.getAddress(),false);
					}
					b=(byte) (b<<1);	
				}										
			}
			else if (entity.m_type==EntityType.WORLD)
			{
	
				short x=packet.m_x;
				if (x<0)
				{
					x=(short) (entity.m_width+x);
				}
				if (x>=entity.m_width)
				{
					x=(short) (x-entity.m_width);
				}
					Chunk c=entity.m_chunks[x][packet.m_y].Clone();
					c.m_position.x=packet.m_x;
					byte b=0b0001;
				for (int i=0;i<4;i++)
				{
					if ((b & packet.m_bitfield)>0)
					{
						Packet_Chunk chunk=new Packet_Chunk(packet.m_instance,packet.m_z, (byte)i, m_timestamp, 
						c, packet.getAddress());
						Transmit(chunk,chunk.getAddress(),false);
					}
					b=(byte) (b<<1);
				}				
			}
					
		}



	}
	
	void HandleAlive(Packet packet)
	{
		
		
	}
	
	void HandleLastreceived(Packet packet)
	{
		if (packet!=null)
		{
			if (m_players.size()>0)
			{
				for (int i=0;i<m_players.size();i++)
				{	
					byte []p=m_players.get(i).getAddress().getAddress();
					byte []c=packet.getAddress().getAddress();
					short d=0;
					for (int j=0;j<c.length;j++)
					{
						if (p[j]==c[j])
						{
							d++;
						}
					}
					if (d==c.length)
					{
						m_players.get(i).m_lastreceived=packet.getTimestamp();
						break;				
					}
				}
			}
		}
	}
	
	void HandlePing(Packet packet)
	{
		//send an authoritative time packet to the client
		//this needs to contain a synchronization time pushed 1/2 the gap between 
		//current time and the timestamp
		int t=m_timestamp-packet.getTimestamp();
		t=t/2;
		Player p=SetLatency(packet.getAddress(),t);	
		t= (int)(t+m_timestamp);

		Packet_Player pl=new Packet_Player(t,packet.getAddress(),p.m_player);
		pl.m_eheight=m_simulation.m_entities.get(0).m_height;
		pl.m_ewidth=m_simulation.m_entities.get(0).m_width;
		pl.m_Etype=m_simulation.m_entities.get(0).m_type;
		m_simulation.PlayerPlacementCheck(p.m_player);
		
	//	Packet p=new Packet(packettype.PING, t, packet.getAddress());
		Transmit(pl,pl.getAddress(),false);

	}
	
	Player SetLatency(InetAddress address, int Tvalue)
	{
		if (m_players.size()>0)
		{
			for (int i=0;i<m_players.size();i++)
			{	
				byte []p=m_players.get(i).getAddress().getAddress();
				byte []c=address.getAddress();
				short d=0;
				for (int j=0;j<c.length;j++)
				{
					if (p[j]==c[j])
					{
						d++;
					}
				}
				if (d==c.length)
				{
					m_players.get(i).m_latency=Tvalue;
					return m_players.get(i);	
				}
			}
		}	
		return null;
	}
	void HandleLastsent(InetAddress address)
	{
		if (m_players.size()>0)
		{
			for (int i=0;i<m_players.size();i++)
			{	
				if (m_players.get(i).getAddress().getAddress()==address.getAddress())
				{
					m_players.get(i).m_lastreceived=m_timestamp;
					break;
				}
			}
		}		
		
	}
	void Transmit(Packet p,InetAddress address, boolean handled)
	{
		if (!handled)
		{
			HandleLastsent(address);
		}
		DatagramPacket dp=p.Encode(address,781);
		try {
			m_outsocket.send(dp);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
		
	
	void HandleMessage(Packet packet)
	{
		Packet_Message ph=(Packet_Message)packet;
//	float ticktime=m_clock/60
		switch (ph.m_msgtype)
		{
		case HANDSHAKE:
			int ticktime=m_timestamp;
			Player p=new Player(ph.m_string,(int)ticktime,packet.getAddress(),ph.m_data);
			p.m_player.m_UID=m_simulation.m_identity;
			Actor_PlayerGhost newghost=new Actor_PlayerGhost(p.m_player);
			m_simulation.m_identity++;
			m_simulation.m_entities.get(p.m_player.m_entity).AddActor(newghost);
			m_players.add(p);
			m_simulation.PlayerPlacementCheck(p.m_player);
			//send an acknowledgement back to the client
			Packet_Message pa=new Packet_Message("server welcomes you", ticktime,MessageType.ACKNOWLEDGEMENT);
			Transmit(pa,packet.getAddress(),false);
						
			break;
		
		}

	}

	public void HandleUpdates()
	{
		Packet p=null;
		while (m_receiver.UpdateWaiting())
		{
			p=m_receiver.TakeUpdate();			
			//handle player processing
			if (p!=null)
			{
				PacketProcessing(p);
			}
		}		
	}
	
	public void Update(float dt)
	{
		m_clock.Update(dt);

		//int catchup=0;
		while (m_clock.Double(m_timestamp))
		{
			
			m_timestamp++;
			boolean s=false;
			
			if ((m_timestamp)%6==0)
			{	
				s=true;
			}
			
			if (s)
			{
				Tick();					
			}		
			
			m_simulation.Update(dt,s);
			Packet p=getPacket();	
			HandleUpdates();
				
		//	catchup++;
		
		}
		//System.out.print("catchups per loop");
		//System.out.print(catchup);
		//System.out.println();
		m_timestamp++;
		boolean s=false;
		
		if ((m_timestamp)%6==0)
		{	
			s=true;
		}
		if (s)
		{
			Tick();					
		}		
		m_simulation.Update(dt,s);
		Packet p=getPacket();	
		HandleUpdates();
		

	}
	
	void Tick()
	{

		if (m_players.size()>0)
		{
			for (int i=0;i<m_players.size();i++)
			{
				//send an update packet
				m_players.get(i).m_lastsent=m_timestamp;	
				Packet_State state=new Packet_State(m_simulation.getStateAt(m_players.get(i).m_player),m_timestamp);
				Transmit(state,m_players.get(i).getAddress(),true);
				
				//check last received, last sent
				if (m_players.get(i).m_lastreceived<(m_timestamp)-300)
				{
					//drop player;
	//				m_players.remove(i);
				
				}
			}
		}
		
	}
	
	public void Discard()
	{
		m_keeprunning=false;
		m_async.interrupt();
		m_receiver.interrupt();	
		m_receiver.Discard();
		m_outsocket.close();
		Savestate();
		
	}
	
	void Savestate() //need to save the state of the server world to a file when the server is shut down
	{
		m_simulation.End();
	}
	
	public Packet getPacket()
	{
		Packet p=null;
		while (m_receiver.PacketWaiting())
		{
			p=m_receiver.TakePacket();			
			//handle player processing
			if (p!=null)
			{
				PacketProcessing(p);
			}
		}
		return p;
		
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		//stand wait update loop goes here
		while (m_keeprunning==true)
		{
			Display.sync(60);
			long t= Sys.getTime();
			//if (t>m_lastframe+0.16F)
			//{
				float dt=(float) (t-m_lastframe)/Game.m_tickspersecond;
			
				m_lastframe=t;
				//timekeeping
				Update(dt);		
			//}
		}
	}
	
	
}
